package de.akabahn.ebd.stw.zdxfc.prs;

import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.*;

import org.apache.log4j.Logger;

import de.akabahn.ebd.comm.dbd.DBDParameter;
import de.akabahn.ebd.dbd.DBDSubscriptionListener;
import de.akabahn.ebd.stw.zdxfc.prs.block.Block1231;
import de.akabahn.ebd.stw.zdxfc.prs.elemente.Gleis;
import de.akabahn.ebd.stw.zdxfc.prs.elemente.Signal;
import de.akabahn.ebd.stw.zdxfc.prs.elemente.Weiche;
import de.akabahn.ebd.stw.zdxfc.prs.fst.FahrStrasse;

/**
 * Implementierung des <code>DBDSubscriptionListener</code> interfaces fuer die zentrale Verwaltung der
 * Aenderungen an den abonnierten Variablen.
 * 
 * @author Peter Eimann
 * @since 1.0
 * @version 1.2
 */
public class ZDXFCDispatcher implements DBDSubscriptionListener {

	private final static Logger LOG = Logger.getLogger(ZDXFCDispatcher.class);
	
	private ZDXFCMain main;
	private boolean sperreAktiv;

	/**
	 * Initialisierung der Klasse
	 * 
	 * @param main Referenz auf die Hauptklasse
	 */
	public ZDXFCDispatcher(ZDXFCMain main) {
		this.main = main;
	}

	/**
	 * Diese Methode wird vom DBD-Adapter aufgerufen, wenn sich der Wert einer abonnierten Variable
	 * geaendert hat.<br>
	 * Innerhalb der Methode wird ausgewertet, welche Variable sich geaendert hat, um die entsprechende Methode
	 * zur Behandlung der Aenderung aufzurufen:<br>
	 * <ol>
	 * <li>Taste gedrueckt: <code>onChangeTasteGedrueckt(dbd)</code></li>
	 * <li>Taste losgelassen: <code>onChangeTasteLosgelassen(dbd)</code></li>
	 * <li>Schalter gedreht: <code>onChangeSchalterGedreht(dbd)</code></li>
	 * <li>Schalter zurueck: <code>onChangeSchalterZurueck(dbd)</code></li>
	 * <li>Hilfshandlung: <code>onChangeHilfshandlung(dbd)</code></li>
	 * <li>Blockschnittstelle: <code>onChangeBlock(dbd)</code></li>
	 * <li>Sperre: <code>onChangeSperre(dbd)</code></li>
	 * <li>Alle anderen Aenderungen: <code>onChangeElement(dbd)</code>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see DBDParameter
	 * @see #onChangeTasteGedrueckt(DBDParameter)
	 * @see #onChangeTasteLosgelassen(DBDParameter)
	 * @see #onChangeSchalterGedreht(DBDParameter)
	 * @see #onChangeSchalterZurueck(DBDParameter)
	 * @see #onChangeHilfshandlung(DBDParameter)
	 * @see #onChangeBlock(DBDParameter)
	 * @see #onChangeSperre(DBDParameter)
	 * @see #onChangeElement(DBDParameter)
	 */
	@Override
	public void onChange(DBDParameter dbd) {
		LOG.debug("onChange: " + dbd.toString());

		if (dbd.getName().startsWith(TASTE_PREFIX) && !sperreAktiv) {
			if (dbd.getValue() == TASTE_GEDRUECKT) {
				onChangeTasteGedrueckt(dbd);
			} else {
				onChangeTasteLosgelassen(dbd);
			}
		} else if (dbd.getName().startsWith(SCHALTER_PREFIX) && !sperreAktiv) {
			if (dbd.getValue() == SCHALTER_GEDREHT) {
				onChangeSchalterGedreht(dbd);
			} else {
				onChangeSchalterZurueck(dbd);
			}
		} else if (dbd.getName().startsWith(HILFSHANDLUNG_PREFIX) && !sperreAktiv) {
			if (dbd.getValue() == HILFSHANDLUNG_AKTIV) {
				onChangeHilfshandlung(dbd);
			}
		} else if ((dbd.getName().equals(BLOCK_1231_ZDM) || dbd.getName().startsWith("12")) && !sperreAktiv) {
			onChangeBlock(dbd);
		} else if (dbd.getName().equals(SPERRE_ZDXFC)) {
			onChangeSperre(dbd);
		} else {
			onChangeElement(dbd);
		}
	}

	/**
	 * Die Aenderung wurde durch einen Tastendruck ausgeloest:<br>
	 * <ol>
	 * <li>Fahrstrassentaste: <code>handleFahrstrasse(FahrStrasse)</code></li>
	 * <li>Blocktaste TEST: <code>Block1231.testeBlockNachZDM()</code></li>
	 * <li>Blocktaste SN: <code>Block1231.tasteGedruecktSn()</code></li>
	 * <li>Blocktaste ANN: <code>Block1231.tasteGedruecktAnn()</code></li>
	 * <li>Blocktaste RED: <code>Block1231.tasteGedruecktRed()</code></li>
	 * <li>Test Aig: <code>Weiche.toggleLageMelder()</code></li>
	 * <li>Test Zones: <code>Weiche.toggleGleisMelder()</code> und <code>Gleis.toggleGleisMelder()</code></li>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see Block1231#tasteGedruecktAnn()
	 * @see Block1231#tasteGedruecktRed()
	 * @see Block1231#tasteGedruecktSn()
	 * @see Block1231#testeBlockNachZDM()
	 * @see Weiche#setZeigeLageMelder(boolean)
	 * @see Weiche#setZeigeGleisMelder(boolean)
	 * @see Gleis#setZeigeGleisMelder(boolean)
	 * @see #handleFahrstrasse(FahrStrasse)
	 */
	private void onChangeTasteGedrueckt(DBDParameter dbd) {
		LOG.debug("onChangeTasteGedrueckt: " + dbd.toString());
	
		for (String fahrstrassenId : main.getFahrStrassen().keySet()) {
			if ((TASTE_PREFIX + fahrstrassenId + "I").equals(dbd.getName())) {
				if (main.getFahrStrassenTasteSperrzeit() == -1) {
					handleFahrstrasse(main.getFahrStrasse(fahrstrassenId));
				} else {
					LOG.debug("Fahrstrassen-Taste Sperrzeit aktiv, Tastenbedienung unwirksam (1)");
				}
				return;
			}
		}

		if (TASTE_BLOCK1231_TEST.equals(dbd.getName())) {
			main.getBlock1231().testeBlockNachZDM();
		} else if (TASTE_BLOCK1231_SN.equals(dbd.getName())) {
			main.getBlock1231().tasteGedruecktSn();
		} else if (TASTE_BLOCK1231_ANN.equals(dbd.getName())) {
			main.getBlock1231().tasteGedruecktAnn();
		} else if (TASTE_BLOCK1231_RED.equals(dbd.getName())) {
			main.getBlock1231().tasteGedruecktRed();
		} else if (TASTE_TEST_AIG.equals(dbd.getName())) {
			main.getDbd().set(MELDER_TASTE_TEST_AIG, BIT0);
			for (String id : main.getWeichen().keySet()) {
				main.getWeiche(id).setZeigeLageMelder(true);
			}
		} else if (TASTE_TEST_ZONES.equals(dbd.getName())) {
			main.getDbd().set(MELDER_TASTE_TEST_ZONES, BIT0);
			for (String id : main.getWeichen().keySet()) {
				main.getWeiche(id).setZeigeGleisMelder(true);
			}
			for (String id : main.getGleise().keySet()) {
				main.getGleis(id).setZeigeGleisMelder(true);
			}
		}
	}

	/**
	 * Die Aenderung wurde durch das Loslassen einer Taste ausgeloest:<br>
	 * <ol>
	 * <li>Blocktaste SN: <code>Block1231.tasteLosgelassenSn()</code></li>
	 * <li>Blocktaste ANN: <code>Block1231.tasteLosgelassenAnn()</code></li>
	 * <li>Blocktaste RED: <code>Block1231.tasteLosgelassenRed()</code></li>
	 * <li>Test Aig: <code>Weiche.toggleLageMelder()</code></li>
	 * <li>Test Zones: <code>Weiche.toggleGleisMelder()</code> und <code>Gleis.toggleGleisMelder()</code></li>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see Block1231#tasteLosgelassenAnn()
	 * @see Block1231#tasteLosgelassenRed()
	 * @see Block1231#tasteLosgelassenSn()
	 * @see Weiche#setZeigeLageMelder(boolean)
	 * @see Weiche#setZeigeGleisMelder(boolean)
	 * @see Gleis#setZeigeGleisMelder(boolean)
	 */
	private void onChangeTasteLosgelassen(DBDParameter dbd) {
		LOG.debug("onChangeTasteLosgelassen: " + dbd.toString());
	
		if (TASTE_BLOCK1231_SN.equals(dbd.getName())) {
			main.getBlock1231().tasteLosgelassenSn();
		} else if (TASTE_BLOCK1231_ANN.equals(dbd.getName())) {
			main.getBlock1231().tasteLosgelassenAnn();
		} else if (TASTE_BLOCK1231_RED.equals(dbd.getName())) {
			main.getBlock1231().tasteLosgelassenRed();
		} else if (TASTE_TEST_AIG.equals(dbd.getName())) {
			main.getDbd().set(MELDER_TASTE_TEST_AIG, 0);
			for (String id : main.getWeichen().keySet()) {
				main.getWeiche(id).setZeigeLageMelder(false);
			}
		} else if (TASTE_TEST_ZONES.equals(dbd.getName())) {
			main.getDbd().set(MELDER_TASTE_TEST_ZONES, 0);
			for (String id : main.getWeichen().keySet()) {
				main.getWeiche(id).setZeigeGleisMelder(false);
			}
			for (String id : main.getGleise().keySet()) {
				main.getGleis(id).setZeigeGleisMelder(false);
			}
		}
	}

	/**
	 * Die Aenderung wurde durch das Drehen eines Schalters ausgeloest:<br>
	 * <ol>
	 * <li>Signal Haltfall-Schalter: <code>Signal.setHaltfallManuell()</code></li>
	 * <li>Blockschalter Bloc: <code>Block1231.sperreBlockVonZDM()</code></li>
	 * <li>Blockschalter Semaphore: <code>Block1231.oeffneSemaphore()</code></li>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see Signal#setHaltfallManuell()
	 * @see Block1231#sperreBlockVonZDM()
	 * @see Block1231#oeffneSemaphore()
	 */
	private void onChangeSchalterGedreht(DBDParameter dbd) {
		LOG.debug("onChangeSchalterGedreht: " + dbd.toString());

		if (SCHALTER_C1.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C1).setHaltfallManuell();
		} else if (SCHALTER_C2.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C2).setHaltfallManuell();
		} else if (SCHALTER_C3.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C3).setHaltfallManuell();
		} else if (SCHALTER_CV4.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV4).setHaltfallManuell();
			main.getSignal(SIGNAL_C2).init();
		} else if (SCHALTER_CV5.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV51).setHaltfallManuell();
			main.getSignal(SIGNAL_CV52).setHaltfallManuell();
			main.getSignal(SIGNAL_CV53).setHaltfallManuell();
		} else if (SCHALTER_CV6.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV6).setHaltfallManuell();
		} else if (SCHALTER_CV7.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV7).setHaltfallManuell();
		} else if (SCHALTER_C8.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C8).setHaltfallManuell();
		} else if (SCHALTER_CV9.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV9).setHaltfallManuell();
		} else if (SCHALTER_C10.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C10).setHaltfallManuell();
		} else if (SCHALTER_CV11.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV11).setHaltfallManuell();
		} else if (SCHALTER_CV12.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV121).setHaltfallManuell();
			main.getSignal(SIGNAL_CV122).setHaltfallManuell();
			main.getSignal(SIGNAL_CV123).setHaltfallManuell();
		} else if (SCHALTER_CV13.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV13).setHaltfallManuell();
		} else if (SCHALTER_CV14.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV14).setHaltfallManuell();
		} else if (SCHALTER_BLOCK1231_BLOC.equals(dbd.getName())) {
			main.getBlock1231().sperreBlockVonZDM();
		} else if (SCHALTER_S4880.equals(dbd.getName())) {
			main.getBlock1231().oeffneSemaphore();
		}
	}
	
	/**
	 * Die Aenderung wurde durch das Zurueckdrehen eines Schalters ausgeloest:<br>
	 * <ol>
	 * <li>Signal Haltfall-Schalter: <code>Signal.resetHaltfallManuell()</code></li>
	 * <li>Blockschalter Bloc: <code>Block1231.entsperreBlockVonZDM()</code></li>
	 * <li>Blockschalter Semaphore: <code>Block1231.schliesseSemaphore()</code></li>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see Signal#resetHaltfallManuell()
	 * @see Block1231#entsperreBlockVonZDM()
	 * @see Block1231#schliesseSemaphore()
	 */
	private void onChangeSchalterZurueck(DBDParameter dbd) {
		LOG.debug("onChangeSchalterZurueck: " + dbd.toString());

		if (SCHALTER_C1.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C1).resetHaltfallManuell();
		} else if (SCHALTER_C2.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C2).resetHaltfallManuell();
		} else if (SCHALTER_C3.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C3).resetHaltfallManuell();
		} else if (SCHALTER_CV4.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV4).resetHaltfallManuell();
		} else if (SCHALTER_CV5.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV51).resetHaltfallManuell();
			main.getSignal(SIGNAL_CV52).resetHaltfallManuell();
			main.getSignal(SIGNAL_CV53).resetHaltfallManuell();
		} else if (SCHALTER_CV6.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV6).resetHaltfallManuell();
		} else if (SCHALTER_CV7.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV7).resetHaltfallManuell();
		} else if (SCHALTER_C8.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C8).resetHaltfallManuell();
		} else if (SCHALTER_CV9.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV9).resetHaltfallManuell();
		} else if (SCHALTER_C10.equals(dbd.getName())) {
			main.getSignal(SIGNAL_C10).resetHaltfallManuell();
		} else if (SCHALTER_CV11.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV11).resetHaltfallManuell();
		} else if (SCHALTER_CV12.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV121).resetHaltfallManuell();
			main.getSignal(SIGNAL_CV122).resetHaltfallManuell();
			main.getSignal(SIGNAL_CV123).resetHaltfallManuell();
		} else if (SCHALTER_CV13.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV13).resetHaltfallManuell();
		} else if (SCHALTER_CV14.equals(dbd.getName())) {
			main.getSignal(SIGNAL_CV14).resetHaltfallManuell();
		} else if (SCHALTER_BLOCK1231_BLOC.equals(dbd.getName())) {
			main.getBlock1231().entsperreBlockVonZDM();
		} else if (SCHALTER_S4880.equals(dbd.getName())) {
			main.getBlock1231().schliesseSemaphore();
		}
	}
	
	/**
	 * Die Aenderung wurde durch das Setzen einer Hilfshandlung ausgeloest:<br>
	 * <ol>
	 * <li>Restart Stellwerk: <code>ZDXFCMain.restart()</code></li>
	 * <li>Fahrstrasse hilfsaufloesen: <code>FahrStrasse.hilfsaufloesen()</code></li>
	 * <li>Block grundstellen: <code>Block1231.hilfsGrundstellung()</code></li>
	 * </ol>
	 * Der Wert der entsprechenden DBD-Variablen wird auf 0 zurueckgesetzt.
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see ZDXFCMain#restart()
	 * @see FahrStrasse#hilfsaufloesen()
	 * @see Block1231#hilfsGrundstellung()
	 */
	private void onChangeHilfshandlung(DBDParameter dbd) {
		LOG.debug("onChangeHilfshandlung: " + dbd.toString());
	
		for (String fahrstrassenId : main.getFahrStrassen().keySet()) {
			if ((HILFSHANDLUNG_PREFIX + fahrstrassenId).equals(dbd.getName())) {
				main.getDbd().set(HILFSHANDLUNG_PREFIX + fahrstrassenId, 0);
				main.getFahrStrasse(fahrstrassenId).hilfsaufloesen();
				return;
			}
		}
		
		if (RESET_STELLWERK.equals(dbd.getName())) {
			main.getDbd().set(RESET_STELLWERK, 0);
			main.restart();
		} else if (RESET_BLOCK_1231.equals(dbd.getName())) {
			main.getDbd().set(RESET_BLOCK_1231, 0);
			main.getBlock1231().hilfsGrundstellung();
		}
	}

	/**
	 * Die Aenderung wurde durch die Blockschnittstelle ausgeloest:<br>
	 * <ol>
	 * <li><code>Block1231.onChange(dbd)</code></li>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see Block1231#onChange(DBDParameter)
	 */
	private void onChangeBlock(DBDParameter dbd) {
		LOG.debug("onChangeBlock" + dbd.toString());

		main.getBlock1231().onChange(dbd);
	}

	/**
	 * Wird aufgerufen, wenn die Sperrvariable <code>CHATEAUESTW</code> geaendert
	 * wurde. Wird die Sperre dabei erneut gesetzt, wird das Stellwerk in Grundstellung
	 * gebracht und keine Bedienungshandlungen mehr entgegengenommen.
	 *  
	 * @param dbd Die geaenderte Variable
	 */
	private void onChangeSperre(DBDParameter dbd) {
		sperreAktiv = dbd.getValue() != 0;

		if (sperreAktiv) {
			LOG.warn("Sperre wurde gesetzt, warte auf Freigabe...");
			System.out.println("Sperre wurde gesetzt, warte auf Freigabe...");

			for (String key : main.getSignale().keySet()) {
				main.getSignal(key).init();
			}
			for (String key : main.getFahrStrassen().keySet()) {
				main.getFahrStrasse(key).hilfsaufloesen();
			}
			main.getBlock1231().hilfsGrundstellung();
			main.getDbd().set(BLOCK_1231_MELDER_AN, 0);
		} else {
			LOG.warn("Sperre wieder freigegeben");
			System.out.println("Sperre wieder freigegeben");
		}
	}

	/**
	 * Alle sonstigen Aenderungen, nacheinander werden aufgerufen:<br>
	 * <ol>
	 * <li><code>Gleis.onChange(dbd)</code> fuer alle Gleise</li>
	 * <li><code>Weiche.onChange(dbd)</code> fuer alle Weichen</li>
	 * <li><code>FahrStrasse.onChange(dbd)</code> fuer alle aktiven Fahrstrassen</li>
	 * </ol>
	 * 
	 * @param dbd Die geaenderte Variable
	 * @see Gleis#onChange(DBDParameter)
	 * @see Weiche#onChange(DBDParameter)
	 * @see FahrStrasse#onChange(DBDParameter)
	 */
	private void onChangeElement(DBDParameter dbd) {
		LOG.debug("onChangeElement" + dbd.toString());
	
		for (String id : main.getGleise().keySet()) {
			main.getGleis(id).onChange(dbd);
		}
		
		for (String id : main.getWeichen().keySet()) {
			main.getWeiche(id).onChange(dbd);
		}
		
		for (String id : main.getFahrStrassen().keySet()) {
			if (main.getFahrStrasse(id).isAktiv()) {
				main.getFahrStrasse(id).onChange(dbd);
			}
		}
	}

	/**
	 * Verarbeitung der Anforderung, wenn eine Fahrstrassentaste gedrueckt wurde:<br>
	 * <ol>
	 * <li>Ist die betroffene Fahrstrasse eingespeichert, wird die Speicherung geloescht</li>
	 * <li>Ist die Fahrstrasse bereits eingestellt (aktiv) wird geprueft, ob die Aufloesebedingungen OK sind,
	 * falls ja, wird bei Verschluss der Fahrstrasse die Aufloesezeit gesetzt, anderenfalls wird die Fahrstrasse
	 * sofort aufgeloest.</li>
	 * <li>Ist die Fahrstrasse noch nicht eingestellt, wird geprueft, ob sie eingestellt werden kann, falls ja,
	 * wird sie eingestellt, anderenfalls wird sie gespeichert</li>
	 * </ol>
	 * Solange die Fahrstrasseneinstellung laeuft, ist keine andere Fahrstrassentaste wirksam, so dass keine
	 * Fahrstrassen parallel eingestellt werden koennen.
	 * 
	 * @param fahrStrasse Die Fahrstrasse, die durch die gedrueckte Taste angefordert wurde
	 * @see FahrStrasse#isGespeichert()
	 * @see FahrStrasse#setGespeichert(boolean)
	 * @see FahrStrasse#isAktiv()
	 * @see FahrStrasse#aufloeseBedinungenOk()
	 * @see FahrStrasse#isVerschlossen()
	 * @see FahrStrasse#setAufloeseZeit(long)
	 * @see FahrStrasse#aufloesen()
	 * @see FahrStrasse#pruefen()
	 * @see FahrStrasse#einstellen()
	 */
	private synchronized void handleFahrstrasse(FahrStrasse fahrStrasse) {
		if (main.getFahrStrassenTasteSperrzeit() == -1) {
			try {
				main.setFahrStrassenTasteSperrzeit(0);
				LOG.debug("Fahrstrassen-Taste Sperrzeit initialisiert");

				if (fahrStrasse.isGespeichert()) {
					fahrStrasse.setGespeichert(false);
					main.setFahrStrassenTasteSperrzeit(-1);
				} else {
					if (fahrStrasse.isAktiv()) {
						if (fahrStrasse.aufloeseBedinungenOk()) {
							if (fahrStrasse.isVerschlossen()) {
								fahrStrasse.setAufloeseZeit(System.currentTimeMillis() + AUFLOESE_VERZOEGERUNG);
							} else {
								fahrStrasse.setAufloeseZeit(AUFLOESUNG_OK);
								fahrStrasse.aufloesen();
							}
						}
						main.setFahrStrassenTasteSperrzeit(-1);
					} else {
						switch (fahrStrasse.pruefen()) {
							case FAHRSTRASSE_OK:
								fahrStrasse.einstellen();
								main.setFahrStrassenTasteSperrzeit(System.currentTimeMillis() + BGTASK_FSTBLOCK_DELAY);
								LOG.debug("Fahrstrassen-Taste Sperrzeit gesetzt");
								break;
							case FAHRSTRASSE_SPEICHERN:
								fahrStrasse.setGespeichert(true);
								main.setFahrStrassenTasteSperrzeit(-1);
								break;
						}
					}
				}
			} catch (RuntimeException e) {
				main.setFahrStrassenTasteSperrzeit(-1);
				throw e;
			}
		} else {
			LOG.debug("Fahrstrassen-Taste Sperrzeit aktiv, Tastenbedienung unwirksam (2)");
		}
	}
}
